<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Slowly Rotating 3D Cyberpunk Box with WebGL</title>
    <style>
        body {
            margin: 0;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            background-color: #000;
        }

        canvas {
            display: block;
        }
    </style>
</head>

<body>
    <canvas id="glCanvas" width="800" height="600"></canvas>
    <script>
        // 以下是获取 canvas 元素和 WebGL 上下文的部分
        // 首先通过 document.getElementById 方法获取页面上 id 为 glCanvas 的 canvas 元素
        // 这个 canvas 元素将作为我们 WebGL 绘图的容器
        const canvas = document.getElementById('glCanvas');
        // 接着使用 canvas 的 getContext 方法获取 WebGL 上下文
        // 如果浏览器支持 WebGL，将返回一个 WebGLRenderingContext 对象，用于后续的绘图操作
        const gl = canvas.getContext('webgl');

        // 检查是否成功获取 WebGL 上下文
        // 如果 gl 为 null，说明浏览器不支持 WebGL，弹出提示框告知用户
        if (!gl) {
            alert('无法初始化 WebGL，你的浏览器可能不支持。');
            return;
        }

        // 顶点着色器代码
        // 顶点着色器是 WebGL 中用于处理顶点数据的程序
        // 它接收顶点的位置和颜色信息，并将其进行变换和传递
        const vertexShaderSource = `
            // 定义一个顶点位置属性，用于接收顶点的位置信息
            attribute vec3 aVertexPosition;
            // 定义一个顶点颜色属性，用于接收顶点的颜色信息
            attribute vec4 aVertexColor;

            // 定义一个统一变量，用于存储投影矩阵
            // 投影矩阵用于将 3D 场景投影到 2D 屏幕上
            uniform mat4 uProjectionMatrix;
            // 定义一个统一变量，用于存储模型视图矩阵
            // 模型视图矩阵用于控制物体的位置、旋转和缩放
            uniform mat4 uModelViewMatrix;

            // 定义一个 varying 变量，用于将顶点颜色传递给片段着色器
            varying lowp vec4 vColor;

            void main(void) {
                // 计算顶点的最终位置，将投影矩阵、模型视图矩阵和顶点位置相乘
                gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aVertexPosition, 1.0);
                // 将顶点颜色传递给片段着色器
                vColor = aVertexColor;
            }
        `;

        // 片段着色器代码
        // 片段着色器是 WebGL 中用于处理每个像素颜色的程序
        // 它接收顶点着色器传递过来的颜色信息，并将其应用到每个像素上
        const fragmentShaderSource = `
            // 接收顶点着色器传递过来的颜色信息
            varying lowp vec4 vColor;

            void main(void) {
                // 将颜色信息赋值给当前像素的颜色
                gl_FragColor = vColor;
            }
        `;

        // 创建着色器函数
        // 该函数用于创建和编译着色器
        function createShader(gl, type, source) {
            // 根据传入的类型（顶点着色器或片段着色器）创建一个新的着色器对象
            const shader = gl.createShader(type);
            // 将着色器代码源赋值给着色器对象
            gl.shaderSource(shader, source);
            // 编译着色器代码
            gl.compileShader(shader);

            // 检查着色器是否编译成功
            if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
                // 如果编译失败，打印错误信息并删除着色器对象
                console.error('着色器编译错误:', gl.getShaderInfoLog(shader));
                gl.deleteShader(shader);
                return null;
            }

            // 如果编译成功，返回着色器对象
            return shader;
        }

        // 创建着色器程序函数
        // 该函数用于创建和链接着色器程序
        function createProgram(gl, vertexShader, fragmentShader) {
            // 创建一个新的着色器程序对象
            const program = gl.createProgram();
            // 将顶点着色器附加到着色器程序中
            gl.attachShader(program, vertexShader);
            // 将片段着色器附加到着色器程序中
            gl.attachShader(program, fragmentShader);
            // 链接着色器程序
            gl.linkProgram(program);

            // 检查着色器程序是否链接成功
            if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
                // 如果链接失败，打印错误信息并删除着色器程序对象
                console.error('着色器程序链接错误:', gl.getProgramInfoLog(program));
                gl.deleteProgram(program);
                return null;
            }

            // 如果链接成功，返回着色器程序对象
            return program;
        }

        // 创建顶点着色器和片段着色器
        const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
        const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);

        // 创建着色器程序
        const shaderProgram = createProgram(gl, vertexShader, fragmentShader);

        // 获取属性和统一变量的位置
        // 通过 gl.getAttribLocation 方法获取顶点位置属性的位置
        const positionAttributeLocation = gl.getAttribLocation(shaderProgram, 'aVertexPosition');
        // 通过 gl.getAttribLocation 方法获取顶点颜色属性的位置
        const colorAttributeLocation = gl.getAttribLocation(shaderProgram, 'aVertexColor');
        // 通过 gl.getUniformLocation 方法获取投影矩阵统一变量的位置
        const projectionMatrixUniformLocation = gl.getUniformLocation(shaderProgram, 'uProjectionMatrix');
        // 通过 gl.getUniformLocation 方法获取模型视图矩阵统一变量的位置
        const modelViewMatrixUniformLocation = gl.getUniformLocation(shaderProgram, 'uModelViewMatrix');

        // 定义立方体的顶点数据
        const positions = [
            // 前面
            -1.0, -1.0,  1.0,
             1.0, -1.0,  1.0,
             1.0,  1.0,  1.0,
            -1.0,  1.0,  1.0,

            // 后面
            -1.0, -1.0, -1.0,
            -1.0,  1.0, -1.0,
             1.0,  1.0, -1.0,
             1.0, -1.0, -1.0,

            // 顶面
            -1.0,  1.0, -1.0,
            -1.0,  1.0,  1.0,
             1.0,  1.0,  1.0,
             1.0,  1.0, -1.0,

            // 底面
            -1.0, -1.0, -1.0,
             1.0, -1.0, -1.0,
             1.0, -1.0,  1.0,
            -1.0, -1.0,  1.0,

            // 右面
             1.0, -1.0, -1.0,
             1.0,  1.0, -1.0,
             1.0,  1.0,  1.0,
             1.0, -1.0,  1.0,

            // 左面
            -1.0, -1.0, -1.0,
            -1.0, -1.0,  1.0,
            -1.0,  1.0,  1.0,
            -1.0,  1.0, -1.0
        ];

        // 定义每个面的颜色
        const faceColors = [
            [0.6, 0.1, 0.8, 1.0], // 紫色系 - 前面
            [0.9, 0.0, 0.9, 1.0], // 紫色系 - 后面
            [0.7, 0.2, 0.9, 1.0], // 紫色系 - 顶面
            [0.8, 0.1, 0.7, 1.0], // 紫色系 - 底面
            [0.9, 0.1, 0.6, 1.0], // 紫色系 - 右面
            [0.7, 0.0, 0.8, 1.0]  // 紫色系 - 左面
        ];

        // 扩展每个面的颜色到每个顶点
        let colors = [];
        for (let j = 0; j < faceColors.length; ++j) {
            const c = faceColors[j];
            colors = colors.concat(c, c, c, c);
        }

        // 创建顶点缓冲区
        const positionBuffer = gl.createBuffer();
        // 将顶点缓冲区绑定到 ARRAY_BUFFER 目标
        gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
        // 将顶点数据存储到缓冲区中
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

        // 创建颜色缓冲区
        const colorBuffer = gl.createBuffer();
        // 将颜色缓冲区绑定到 ARRAY_BUFFER 目标
        gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
        // 将颜色数据存储到缓冲区中
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);

        // 定义索引数据
        const indices = [
            0,  1,  2,      0,  2,  3,    // 前面
            4,  5,  6,      4,  6,  7,    // 后面
            8,  9,  10,     8,  10, 11,   // 顶面
            12, 13, 14,     12, 14, 15,   // 底面
            16, 17, 18,     16, 18, 19,   // 右面
            20, 21, 22,     20, 22, 23    // 左面
        ];

        // 创建索引缓冲区
        const indexBuffer = gl.createBuffer();
        // 将索引缓冲区绑定到 ELEMENT_ARRAY_BUFFER 目标
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
        // 将索引数据存储到缓冲区中
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);

        // 设置视口
        // 将视口设置为整个 canvas 的大小
        gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

        // 清除颜色缓冲区
        // 设置清除颜色为黑色，并启用深度测试
        gl.clearColor(0.0, 0.0, 0.0, 1.0);
        gl.enable(gl.DEPTH_TEST);

        // 矩阵运算相关函数开始
        // 以下是一些基本的矩阵运算函数，用于实现矩阵的创建、乘法、旋转等操作
        // 这些函数是手动实现的，避免使用 CDN 引入外部库

        // 创建一个 4x4 的单位矩阵
        function mat4Create() {
            return [
                1, 0, 0, 0,
                0, 1, 0, 0,
                0, 0, 1, 0,
                0, 0, 0, 1
            ];
        }

        // 矩阵乘法函数
        function mat4Multiply(out, a, b) {
            const a00 = a[0 * 4 + 0];
            const a01 = a[0 * 4 + 1];
            const a02 = a[0 * 4 + 2];
            const a03 = a[0 * 4 + 3];
            const a10 = a[1 * 4 + 0];
            const a11 = a[1 * 4 + 1];
            const a12 = a[1 * 4 + 2];
            const a13 = a[1 * 4 + 3];
            const a20 = a[2 * 4 + 0];
            const a21 = a[2 * 4 + 1];
            const a22 = a[2 * 4 + 2];
            const a23 = a[2 * 4 + 3];
            const a30 = a[3 * 4 + 0];
            const a31 = a[3 * 4 + 1];
            const a32 = a[3 * 4 + 2];
            const a33 = a[3 * 4 + 3];
            const b00 = b[0 * 4 + 0];
            const b01 = b[0 * 4 + 1];
            const b02 = b[0 * 4 + 2];
            const b03 = b[0 * 4 + 3];
            const b10 = b[1 * 4 + 0];
            const b11 = b[1 * 4 + 1];
            const b12 = b[1 * 4 + 2];
            const b13 = b[1 * 4 + 3];
            const b20 = b[2 * 4 + 0];
            const b21 = b[2 * 4 + 1];
            const b22 = b[2 * 4 + 2];
            const b23 = b[2 * 4 + 3];
            const b30 = b[3 * 4 + 0];
            const b31 = b[3 * 4 + 1];
            const b32 = b[3 * 4 + 2];
            const b33 = b[3 * 4 + 3];
            out[0] = b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30;
            out[1] = b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31;
            out[2] = b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32;
            out[3] = b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33;
            out[4] = b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30;
            out[5] = b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31;
            out[6] = b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32;
            out[7] = b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33;
            out[8] = b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30;
            out[9] = b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31;
            out[10] = b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32;
            out[11] = b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33;
            out[12] = b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30;
            out[13] = b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31;
            out[14] = b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32;
            out[15] = b30 * a03 + b31 * a13 + b32 * a23 + b33